/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */

package org.osdtsystem.estimator;

import org.osdtsystem.hierarchy.Hierarchy;
import org.osdtsystem.hierarchy.HClass;
import java.util.Arrays;
import java.util.Collections;
import java.util.Map;
import org.osdtsystem.utils.CacheMap;
import org.osdtsystem.utils.Storage;
import org.osdtsystem.dataset.Dataset;
import org.osdtsystem.dataset.HierarchyView;

/**
 * Abstract estimator which should be extended by all estimators.
 * @param <H> the type of hierarchy classes
 * @param <HT> the type of the hierarchy
 * @author Matthias Buch-Kromann
 */
public abstract class AbstractHierarchyEstimator<H extends HClass, HT extends Hierarchy<H>>
        extends AbstractEstimator implements HierarchyEstimator<H,HT> {
    final HT hierarchy;
    // final HierarchyView<H> view;
    final int cacheSize;
    final Map<H[],Double> evaluateUnionCache;

    AbstractHierarchyEstimator(final Storage storage, final HT hierarchy, 
            final HierarchyView<H> view, final Dataset trainingData, final int cacheSize) {
        super(storage, view, trainingData);
        this.hierarchy = hierarchy;

        // Create union cache
        this.cacheSize = cacheSize;
        if (cacheSize == 0) {
            this.evaluateUnionCache = Collections.emptyMap();
        } else {
            this.evaluateUnionCache = new CacheMap<H[], Double>(cacheSize);
        }
    }

    AbstractHierarchyEstimator(final Storage storage, final HT hierarchy, final HierarchyView<H> view, Dataset trainingData) {
        this(storage, hierarchy, view, trainingData, 0);
    }

    @Override
    public final HT hierarchy() {
        return hierarchy;
    }

    @Override
    @SuppressWarnings("unchecked")
    public final HierarchyView<H> view() {
        return (HierarchyView<H>) view;
    }

    @Override
    @SuppressWarnings("unchecked")
    public double evaluateUnion(final H... union) {
        // Check whether union has length 1
        if (union.length == 1)
            return evaluate(union[0]);

        // Lookup union in cache
        Double cachedValue = evaluateUnionCache.get(union);
        if (cachedValue != null)
            return cachedValue;

        // Compute value of class xn in union x1 ∪ ... ∪ xn.
        H xn = union[union.length - 1];

        // Compute value of rest union x1 ∪ ... ∪ x[n-1].
        H[] xrest = Arrays.copyOf(union, union.length - 1);
        double value = evaluateUnion(xrest);

        // Now intersect rest union with yN and subtract resulting union
        for (int i = 0; i < xrest.length; ++i) {
            xrest[i] = (H) hierarchy().intersection(xn, xrest[i]);
        }
        xrest = hierarchy.union(xrest);

        // Compute new value
        double newValue = value + (evaluate(xn) - evaluateUnion(xrest));

        // Cache value
        if (cacheSize > 0)
            evaluateUnionCache.put(union, newValue);
        return newValue;
    }

    @Override
    @SuppressWarnings("unchecked")
    public double evaluateDifference(final H x, final H[] union) {
        // Intersect union with x
        H[] intersectedUnion = Arrays.copyOf(union, union.length);
        for (int i = 0; i < intersectedUnion.length; ++i) {
            intersectedUnion[i] = (H) hierarchy.intersection(x, union[i]);
        }
        intersectedUnion = hierarchy.union(intersectedUnion);

        // Return F(x - union) = F(x) - F(x & union)
        return evaluate(x) - evaluateUnion(intersectedUnion);
    }

    @Override
    public double evaluate(Dataset dataset, int identifier) {
        return evaluate(hierarchy.dataClass(view(), dataset, identifier));
    }
}
